% =============================================================================
% Automated modeling in process-based qualitative reasoning
%  ** Dependency interaction finding module
% 
% April - July 2007
% 
% Hylke Buisman   - hbuisman@science.uva.nl
% =============================================================================

%==============================================================================
% INTERACTION IDENTIFICATION
%==============================================================================
	
% Check whether it is necessary to find dependency interactions.
% At the moment this only checks for undriven clusters
% If one is found, it continues to search for interactions
% find_interactions/2
% find_interactions(+ModelIn, -ModelOut)
find_interactions(naive_model(Clusters, Drives, Deps), 
		naive_model(Clusters, NDrives, Deps)) :-
	append(Drives, Deps, AllDeps),
	member(C, Clusters),
	get_cluster_path(C, Deps, CPath),
	beginning_of_path(To, CPath),
	end_of_path(From, CPath),
	\+ member(model_dependency(_, [To, _]), AllDeps),
	\+ member(model_dependency(_, [_,From]), AllDeps),
	brute_force_interaction_search(InteractionList),
	flatten(InteractionList, Interactions),
	append(Interactions, Drives, NDrives), !.
find_interactions(Model, Model).
	
	
% Does a more or less brute-froce search for influence interactions
% brute_force_interaction_search/1
% brute_force_interaction_search(-Interactions)
brute_force_interaction_search(Interacts) :-
	% find all triples
	get_quantity_names(Qs),
	findall(S, engine:state(S, _), States),
	loop_interactors(Qs, States, Interacts).

	
% Wrapper for loop_interactors/4
% loop_interactors/3
% loop_interactors(+Quantities, +States, -Interactions)
loop_interactors(Qs, States, Interacts) :-
		loop_interactors(Qs, Qs, States, Interacts).

		
% Loop over all quantities to search whether an interaction
% is consistent on that quantity. In other words: is the quantity
% influenced by interactions?
% loop_interactors/4
% loop_interactors(+Quantities, +Quantitities, +States, -Interactions)
loop_interactors([], _, _, []).
loop_interactors([Q1|Rest], Qs, States, [Interacts2|Interacts]) :-
	loop_interactors(Rest, Qs, States, Interacts),
	findall([Q1, Q2, Q3], 
		(
		member(Q2,Qs),
		member(Q3,Qs),
		Q2 \= Q1,
		Q3 \= Q1,
		Q2 \= Q3
		), QTriples),
	do_interaction_find(QTriples, States, [], [], Interacts2).
	
	
% For a single quantity find, if applicable, all interaction
% working on it. Adds opposing pairs until the state-graph is covered
% apart from where the interacted on quantity is stable.
% TODO: Change from depth-first to breadth-first adding of opposing pairs
% do_interaction_find/5
% do_interaction_find(+InteractionTriples, +States, 
%					  +Covered, +Interacts, -InteractionDependencies)
do_interaction_find([], _, _, _, []).
do_interaction_find([[Q1, _, _]|_], States, Covered, Interacts, Deps2) :- % Stop condition
	subtract(States, Covered, Remaining),
	are_stable(Q1, Remaining),
	filter_subsets(Interacts, Interacts, NInteracts),
	interacts_to_dependencies(NInteracts, Deps),
	remove_inconsistencies(Deps, Deps2).
do_interaction_find([[Q1, Q2, Q3]|T], States, Covered, Interacts, Out) :-
	findall(S, 
		(
		member(S, States), 
		dependency_consistency_constraint(S, [Q1, Q2, Q3], [inf_pos_by(Q1, Q2), inf_neg_by(Q1, Q3)])
		), L),
	subtract(L, Covered, Diff),
	Diff \= [],!,
	union(L, Covered, NCovered),
	do_interaction_find(T, States, NCovered, [[Q1, Q2, Q3]/L|Interacts], Out).
do_interaction_find([_|T], States, Covered, Interacts, Out) :-
	do_interaction_find(T, States, Covered, Interacts, Out).

	
	
%==============================================================================
% AUXILLIARY PREDICATES
%==============================================================================

% Convert a list of interacting quantities, to a list of dependencies to 
% add to the model
% interacts_to_dependencies/2
% interacts_to_dependencies(+Interacts, -Dependencies)
interacts_to_dependencies([], []).
interacts_to_dependencies([[Q1, Q2, Q3]|T], [model_dependency(inf_pos_by, [Q1, Q2]), model_dependency(inf_neg_by,[Q1, Q3])|Deps]) :-
	interacts_to_dependencies(T, Deps).
	
	
% If a list of dependencies contains conflicts, the list
% is returned empty. Alternative might be to only
% remove the conflicting dependencies (Has same effect?)
% remove_inconsistencies/2
% remove_inconsistencies(+DependenciesIn, -DependenciesOut)
remove_inconsistencies(Dependencies, []) :-
	member(model_dependency(Dep1, [Q1, Q2]), Dependencies),
	member(model_dependency(Dep2, [Q3, Q4]), Dependencies),
	D1 =.. [Dep1, Q1, Q2],
	D2 =.. [Dep2, Q3, Q4],
	D1 \= D2,
	dependency_conflict(D1, D2).
remove_inconsistencies(Dependencies, Dependencies).


% Given a list of sets, it removes all sets that are a subset
% of any other set in the list. In this case the sets are assumed
% to contain the states that are covered by an interaction.
% filter_subsets/4
% filter_subsets(+CoveringSet, +AccSet, -FilteredSets)
filter_subsets([], _, []).
filter_subsets([[Q1, Q2, Q3]/Set|Rest], Sets, NewSets) :-
	delete(Sets, [Q1, Q2, Q3]/Set, NSets),
	merge_sets(NSets, MSets),
	subtract(Set, MSets, Result),
	are_stable(Q1, Result),
	filter_subsets(Rest, NSets, NewSets).
filter_subsets([Item/_|Rest], Sets, [Item|NewSets]) :-
	filter_subsets(Rest, Sets, NewSets).

	
% Checks if a given Quantity is stable in all given states
% are_stable/2
% are_stable(+Quantity, +States)
are_stable(_, []).
are_stable([], _).
are_stable(Q1, [S|Rest]) :-
	get_quantity_values(S, derivative, Q1, zero),
	are_stable(Q1, Rest).

	
% Merge list of covering sets, while
% removing the triple indication.
% merge_sets/2
% merge_sets(+SetList, -MergedSet)
merge_sets([], []). 
merge_sets([_/S|Rest], NSets2) :-
	merge_sets(Rest, NSets),
	union(S, NSets, NSets2).